#pragma once
#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>
using namespace std;

template <typename T>
class BlockQueue
{
private:
    bool IsFull()
    {
        return _block_queue.size() == _capacity;
    }
    bool IsEmpty()
    {
        return _block_queue.empty();
    }

public:
    BlockQueue(int capacity)
        : _capacity(capacity)
    {
        productor_wait_num = 0;
        consumer_wait_num = 0;
        pthread_mutex_init(&(_mutex), nullptr);
        pthread_cond_init(&(_product_cond), nullptr);
        pthread_cond_init(&(_consume_cond), nullptr);
    }

    void EnQueue(const T &data) // 生产者存放数据
    {
        pthread_mutex_lock(&(_mutex));
        // if(IsFull()) --- while(IsFull)

        // 场景:假设一共有5个消费者,1个生产者
        // 最初阶段,阻塞队列为空,消费者全部在指定条件变量下的等待队列中进行等待,若生产者只生产一个数据;
        // 生产者采用pthread_cond_broadcast()接口将所有指定条件变量下的等待队列中的消费者全部唤醒;
        // 所有的消费者线程全部竞争同一把互斥锁,假设消费者1线程竞争成功,消费者1线程可以获取数据,然后通知生产者继续生产数据;
        // 消费者2、消费者3、消费者4、消费者5已经被全部唤醒,不会在指定的条件变量下等待(互斥锁下进行等待)&&生产者被唤醒，一旦消费者1解锁
        // 消费者2、消费者3、消费者4、消费者5与生产者争抢同一把锁,一旦某一个消费者竞争互斥锁成功,由于阻塞队列中的没有数据
        // 后面消费者的行为是未可知的;------>当条件没有满足时,唤醒在该条件下等待的线程,导致该线程被伪唤醒,发生未可知的行为！！

        // 场景2:
        //  pthread_cond_wait()函数调用失败,线程无法在指定的条件变量下进行等待,直接向下执行代码

        // 解决方案:对于消费者,若竞争互斥锁成功,重新判断阻塞队列是否为空,若阻塞队列为空, 继续让消费者在指定的条件变量下进行等待！
        // 对于生产者,若竞争互斥锁成功,重新判断阻塞队列是否为满,若阻塞队列为满, 继续让生产者者在指定的条件变量下进行等待！
        while (IsFull())
        {
            productor_wait_num++;
            pthread_cond_wait(&(_product_cond), &(_mutex)); // 只要存在等待,必定存在唤醒操作！
            productor_wait_num--;
            // pthread_cond_wait()函数作用:
            // a.调用该函数的线程在指定的条件变量下进行等待;
            // b.自动释放曾经持有的互斥锁;
            // c.当条件满足时,该线程被唤醒,必须重新竞争互斥锁,竞争成功,继续从此位置向下运行;
            //  pthread_cond_wait()调用之前与调用之后都保持持有互斥锁的状态,保证临界区的安全
        }
        _block_queue.push(data);

        if(consumer_wait_num>0)
        {
            pthread_cond_signal(&(_consume_cond));
        }

        pthread_mutex_unlock(&(_mutex));
    }

    void DeQueue(T *data) // 消费者获取数据
    {
        pthread_mutex_lock(&(_mutex));
        while (IsEmpty()) // while()循环保证代码的健壮性
        {
            consumer_wait_num++;
            pthread_cond_wait(&(_consume_cond), &(_mutex));
            consumer_wait_num--;
        }

        *data = _block_queue.front();
        _block_queue.pop();

        if(productor_wait_num>0)
        {
            pthread_cond_signal(&(_product_cond));
        }
        pthread_mutex_unlock(&(_mutex));
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&(_mutex));
        pthread_cond_destroy(&(_product_cond));
        pthread_cond_destroy(&(_consume_cond));
    }

private:
    queue<T> _block_queue;
    int _capacity;
    pthread_mutex_t _mutex;
    pthread_cond_t _product_cond;
    pthread_cond_t _consume_cond;

    // 若阻塞队列不为满,则生产者一直在生产,消费者没有必要唤醒生产者
    int productor_wait_num; // 当前生产者等待的数量

    // 若阻塞队列不为空,则消费者一直在消费,生产者没有必要唤醒消费者
    int consumer_wait_num; // 当前消费者等待的数量
};